//Copyright (C) 2000-2006 Gnanasekaran Swaminathan <sekar@sekar.net>
//
//Permission is granted to use at your own risk and distribute this software
//in source and  binary forms provided  the above copyright notice and  this
//paragraph are  preserved on all copies.  This software is provided "as is"
//with no express or implied warranty.

#include <ctime>
#include <cstdio>
#include <malloc.h>

#include <string>

#include "socklib.h"
#include "sock.h"

namespace socklib {
  extern const char* sockerror_detailed(int err); // defined in sockerror.h
}

socklib::inetsocket::sockdata socklib::inetsocket::onlyone;

socklib::inetaddr::inetaddr()
{
  sin_family      = AF_INET;
  sin_port        = 0;
  sin_addr.s_addr = INADDR_ANY;  
}

socklib::inetaddr::inetaddr(unsigned short port)
{
  sin_family      = AF_INET;
  sin_port        = htons(port);
  sin_addr.s_addr = INADDR_ANY;
}

socklib::inetaddr::inetaddr(const char* host, unsigned short port)
{
  sin_family      = AF_INET;
  sin_port        = htons(port);
  sin_addr.s_addr = inet_addr(host);

  if (sin_addr.s_addr == INADDR_NONE) {
    struct hostent* hent = gethostbyname(host);
    if (hent == 0)
      throw socklib::exception("sock:inetaddr: gethostbyname(%s); $s", host) ;
    memcpy(&sin_addr.s_addr, hent->h_addr, hent->h_length);
  }

  if (sin_addr.s_addr == INADDR_NONE)
    throw socklib::exception("sock:inetaddr: gethostbyname(%s); $s", host) ;
}

socklib::inetaddr::inetaddr(unsigned long addr, unsigned short port)
{
  sin_family      = AF_INET;
  sin_port        = htons(port);
  sin_addr.s_addr = addr;
}

socklib::inetaddr::inetaddr(const struct sockaddr_in& addr)
{
  sin_family      = addr.sin_family;
  sin_port        = addr.sin_port;
  sin_addr.s_addr = addr.sin_addr.s_addr;
}

bool
socklib::inetaddr::operator==(const socklib::inetaddr& b) const
{
  return sin_family == b.sin_family && sin_addr.s_addr == b.sin_addr.s_addr && sin_port == b.sin_port;
}

bool
socklib::inetaddr::operator<(const socklib::inetaddr& b) const
{
  return sin_family < b.sin_family
         || sin_family == b.sin_family && sin_addr.s_addr < b.sin_addr.s_addr
         || sin_family == b.sin_family && sin_addr.s_addr == b.sin_addr.s_addr && sin_port < b.sin_port;
}

void
socklib::inetaddr::set_local_addr()
{
  sin_family      = AF_INET;
  sin_port        = 0;
  sin_addr.s_addr = inet_addr("127.0.0.1");

  char hostname[256]; hostname[255] = 0;
  if (gethostname(hostname, 255) == SOCKET_ERROR) strcpy(hostname, "localhost");

  struct hostent* hent = gethostbyname(hostname);
  if (hent == 0) return;

  memcpy(&sin_addr.s_addr, hent->h_addr, hent->h_length);
}

std::string
socklib::inetaddr::get_host_name() const
{
  struct hostent* hent = gethostbyaddr((const char*)&sin_addr.s_addr, sizeof(unsigned long), AF_INET);

  if (hent)
    return std::string(hent->h_name);
  
  return get_addr_string(true);
}

std::string
socklib::inetaddr::get_addr_string(bool shortform) const
{
  const char* addr = inet_ntoa(sin_addr);

  if (shortform) return std::string(addr);

  char buf[256];
  _snprintf(buf, 256, "%s:%d", addr, ntohs(sin_port));
  return std::string(buf);
}

socklib::inetsocket::sockdata::sockdata()
{
  WORD winsock_version = MAKEWORD(2,0);

  int err = WSAStartup(winsock_version, &wsadata);
  if (err != 0)
    throw socklib::exception("WSAStartup: %s", socklib::sockerror_detailed(err));
}

socklib::inetsocket::sockdata::~sockdata()
{
  WSACleanup();
}

socklib::inetsocket::inetsocket(int socktype, int protocol)
{
  open(socktype, protocol);
}

socklib::inetsocket::~inetsocket()
{
  if (sock != INVALID_SOCKET) ::closesocket(sock);
  sock = INVALID_SOCKET;
}

void
socklib::inetsocket::open(int socktype, int protocol)
{
  close();
  sock = ::socket(AF_INET, socktype, protocol);
  if (sock == INVALID_SOCKET)
    throw socklib::exception("sock:inetsocket:open: $s");
}

void
socklib::inetsocket::close()
{
  if (sock != INVALID_SOCKET) ::closesocket(sock);
  sock = INVALID_SOCKET;
}

void
socklib::inetsocket::bind(unsigned short port)
{
  inetaddr iaddr(port);
  bind(iaddr);
}

void
socklib::inetsocket::bind(const char* host, unsigned short port)
{
  inetaddr iaddr(host, port);
  bind(iaddr);
}

void
socklib::inetsocket::bind(unsigned long addr, unsigned short port)
{
  inetaddr iaddr(addr, port);
  bind(iaddr);
}

void
socklib::inetsocket::bind(const sockaddr_in& addr)
{
  int err = ::bind(sock, (const struct sockaddr*)&addr, sizeof(struct sockaddr_in));
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:bind: $s");
}

void
socklib::inetsocket::connect(const char* host, unsigned short port)
{
  inetaddr iaddr(host, port);
  connect(iaddr);
}

void
socklib::inetsocket::connect(unsigned long addr, unsigned short port)
{
  inetaddr iaddr(addr, port);
  connect(iaddr);
}

void
socklib::inetsocket::connect(const struct sockaddr_in& addr)
{
  int err = ::connect(sock, (const struct sockaddr*)&addr, sizeof(struct sockaddr_in));
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:connect: $s");
}

void
socklib::inetsocket::getpeername(struct sockaddr_in& addr)
{
  int addrlen = sizeof(struct sockaddr_in);
  int err = ::getpeername(sock, (struct sockaddr*)&addr, &addrlen);
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:getpeername: $s");
}

void
socklib::inetsocket::getsockname(struct sockaddr_in& addr)
{
  int addrlen = sizeof(struct sockaddr_in);
  int err = ::getsockname(sock, (struct sockaddr*)&addr, &addrlen);
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:getsockname: $s");
}

std::string
socklib::inetsocket::getlocalhost()
{
  struct sockaddr_in addr;
  getsockname(addr);

  HOSTENT* hent = gethostbyaddr((const char*)&addr.sin_addr.s_addr, sizeof(unsigned long), AF_INET);
  if (hent) return std::string(hent->h_name);

  char localhost[256];
  if (gethostname(localhost, 256) == SOCKET_ERROR) return "localhost";

  return localhost;
}

void
socklib::inetsocket::listen(int backlog)
{
  if (backlog == 0) backlog = SOMAXCONN;

  int err = ::listen(sock, backlog);
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:listen: $s");
}

SOCKET
socklib::inetsocket::accept(struct sockaddr_in& addr)
{
  int addrlen = sizeof(struct sockaddr_in);
  SOCKET csock = ::accept(sock, (struct sockaddr*)&addr, &addrlen);
  if (csock == INVALID_SOCKET) {
    if (WSAGetLastError() == WSAEINTR) throw (int)WSAEINTR;
    throw socklib::exception("sock:inetsocket:accept: $s");
  }
  return csock;
}

int
socklib::inetsocket::recv(void* buf, int buflen, int flags)
{
  int rd = ::recv(sock, (char*)buf, buflen, flags);
  if (rd == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:recv: $s");
  return rd;
}

int
socklib::inetsocket::recvfrom(struct sockaddr_in& addr,
		                 void* buf, int buflen, int flags)
{
  int addrlen = sizeof(struct sockaddr_in);
  int rd = ::recvfrom(sock, (char*)buf, buflen, flags, (struct sockaddr*)&addr, &addrlen);
  if (rd == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:recvfrom: $s");
  return rd;
}

int
socklib::inetsocket::recv_nbio(void* buf, int buflen, int flags)
{
  int rd = ::recv(sock, (char*)buf, buflen, flags);
  if (rd == SOCKET_ERROR) {
    int err = WSAGetLastError();
    if (err == WSAEWOULDBLOCK) return SOCKET_ERROR;
    throw socklib::exception("sock:inetsocket:recv_nbio: $s");
  }
  return rd;
}

void
socklib::inetsocket::send(const void* buf, int buflen, int flags)
{
  while(buflen) {
    int wr = ::send(sock, (const char*)buf, buflen, flags);
    if (wr == SOCKET_ERROR)
      throw socklib::exception("sock:inetsocket:send: $s");
    if (wr == 0)
      throw socklib::exception("sock:inetsocket:send: peer connection closed");
    buflen -= wr;
  }
}

void
socklib::inetsocket::sendto(const struct sockaddr_in& addr,
		                    const void* buf, int buflen, int flags)
{
  int addrlen = sizeof(struct sockaddr_in);

  while(buflen) {
    int wr = ::sendto(sock, (const char*)buf, buflen, flags, (const struct sockaddr*)&addr, addrlen);
    if (wr == SOCKET_ERROR)
      throw socklib::exception("sock:inetsocket:sendto: $s");
    if (wr == 0)
      throw socklib::exception("sock:inetsocket:sendto: peer connection closed");
    buflen -= wr;
  }
}

bool
socklib::inetsocket::is_readready(const struct timeval* tv)
{
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(sock, &fds);

  int result = ::select((int)sock+1, &fds, 0, 0, tv);
  if (result == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:is_readready: $s");
  
  return FD_ISSET(sock, &fds) != 0;
}

bool
socklib::inetsocket::is_writeready(const struct timeval* tv)
{
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(sock, &fds);

  int result = ::select((int)sock+1, 0, &fds, 0, tv);
  if (result == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:is_write_ready: $s");

  return FD_ISSET(sock, &fds) != 0;
}

bool
socklib::inetsocket::is_exceptready(const struct timeval* tv)
{
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(sock, &fds);

  int result = ::select((int)sock+1, 0, 0, &fds, tv);
  if (result == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:is_exceptionready: $s");

  return FD_ISSET(sock, &fds) != 0;
}

bool
socklib::inetsocket::is_readready(int secs, int usecs)
{
  struct timeval tv;
  tv.tv_sec  = secs;
  tv.tv_usec = usecs;
  return is_readready(&tv);
}

bool
socklib::inetsocket::is_writeready(int secs, int usecs)
{
  struct timeval tv;
  tv.tv_sec  = secs;
  tv.tv_usec = usecs;
  return is_writeready(&tv);
}

bool
socklib::inetsocket::is_exceptready(int secs, int usecs)
{
  struct timeval tv;
  tv.tv_sec  = secs;
  tv.tv_usec = usecs;
  return is_exceptready(&tv);
}

void
socklib::inetsocket::shutdown(int how)
{
  int err = ::shutdown(sock, how);
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:shutdown: $s");
}

void
socklib::inetsocket::ioctl_nbio(bool set)
{
  unsigned long cmdarg = set;
  ioctl(FIONBIO, cmdarg);
}

unsigned long
socklib::inetsocket::ioctl_nread()
{
  unsigned long cmdarg = 0;
  ioctl(FIONREAD, cmdarg);
  return cmdarg;
}

bool
socklib::inetsocket::ioctl_atmark()
{
  unsigned long cmdarg = 0;
  ioctl(SIOCATMARK, cmdarg);
  return cmdarg != 0;
}

void
socklib::inetsocket::ioctl(int cmd, unsigned long& cmdarg)
{
  int err = ::ioctlsocket(sock, cmd, &cmdarg);
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:ioctl: cmd=%d cmdarg=0x%08x $s", cmd, cmdarg);
}

bool
socklib::inetsocket::getopt_acceptconn()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_ACCEPTCONN, &optval, optlen);
  return optval != 0;
}

bool
socklib::inetsocket::getopt_broadcast()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_BROADCAST, &optval, optlen);
  return optval != 0;
}

bool
socklib::inetsocket::getopt_debug()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_DEBUG, &optval, optlen);
  return optval != 0;
}

bool
socklib::inetsocket::getopt_dontlinger()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_DONTLINGER, &optval, optlen);
  return optval != 0;
}

bool
socklib::inetsocket::getopt_dontroute()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_DONTROUTE, &optval, optlen);
  return optval != 0;
}

int
socklib::inetsocket::getopt_error()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_ERROR, &optval, optlen);
  return optval;
}

bool
socklib::inetsocket::getopt_keepalive()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
  return optval != 0;
}

struct linger
socklib::inetsocket::getopt_linger()
{
  struct linger optval;
  int optlen = sizeof(struct linger);
  getsockopt(SOL_SOCKET, SO_LINGER, &optval, optlen);
  return optval;
}

unsigned int
socklib::inetsocket::getopt_maxmsgsize()
{
  unsigned int optval = 0;
  int optlen = sizeof(unsigned int);
  getsockopt(SOL_SOCKET, SO_MAX_MSG_SIZE, &optval, optlen);
  return optval;
}

bool
socklib::inetsocket::getopt_oobinline()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_OOBINLINE, &optval, optlen);
  return optval != 0;
}

WSAPROTOCOL_INFO
socklib::inetsocket::getopt_protocolinfo()
{
  WSAPROTOCOL_INFO optval;
  int optlen = sizeof(WSAPROTOCOL_INFO);
  getsockopt(SOL_SOCKET, SO_PROTOCOL_INFO, &optval, optlen);
  return optval;
}

int
socklib::inetsocket::getopt_rcvbuf()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_RCVBUF, &optval, optlen);
  return optval;
}

bool
socklib::inetsocket::getopt_reuseaddr()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
  return optval != 0;
}

int
socklib::inetsocket::getopt_sndbuf()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_SNDBUF, &optval, optlen);
  return optval;
}

int
socklib::inetsocket::getopt_type()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(SOL_SOCKET, SO_TYPE, &optval, optlen);
  return optval;
}

bool
socklib::inetsocket::getopt_tcpnodelay()
{
  int optval = 0;
  int optlen = sizeof(int);
  getsockopt(IPPROTO_TCP, TCP_NODELAY, &optval, optlen);
  return optval != 0;
}

void
socklib::inetsocket::getsockopt(int level, int optname, void* optval, int& optlen)
{
  int err = ::getsockopt(sock, level, optname, (char*)optval, &optlen);
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:getsockopt: $s");
}

void
socklib::inetsocket::setopt_broadcast(bool set)
{
  int optval = set;
  setsockopt(SOL_SOCKET, SO_BROADCAST, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_debug(bool set)
{
  int optval = set;
  setsockopt(SOL_SOCKET, SO_DEBUG, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_dontlinger(bool set)
{
  int optval = set;
  setsockopt(SOL_SOCKET, SO_DONTLINGER, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_dontroute(bool set)
{
  int optval = set;
  setsockopt(SOL_SOCKET, SO_DONTROUTE, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_keepalive(bool set)
{
  int optval = set;
  setsockopt(SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_linger(const struct linger& lg)
{
  setsockopt(SOL_SOCKET, SO_LINGER, &lg, sizeof(struct linger));
}

void
socklib::inetsocket::setopt_linger(unsigned short onoff, unsigned short timeout)
{
  struct linger lg;
  lg.l_onoff  = onoff;
  lg.l_linger = timeout;

  setopt_linger(lg);
}

void
socklib::inetsocket::setopt_oobinline(bool set)
{
  int optval = set;
  setsockopt(SOL_SOCKET, SO_OOBINLINE, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_rcvbuf(int size)
{
  setsockopt(SOL_SOCKET, SO_RCVBUF, &size, sizeof(int));
}

void
socklib::inetsocket::setopt_reuseaddr(bool set)
{
  int optval = set;
  setsockopt(SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_sndbuf(int size)
{
  setsockopt(SOL_SOCKET, SO_SNDBUF, &size, sizeof(int));
}

void
socklib::inetsocket::setopt_tcpnodelay(bool set)
{
  int optval = set;
  setsockopt(IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(int));
}

void
socklib::inetsocket::setopt_rcvtimeo(int milliseconds)
{
  setsockopt(SOL_SOCKET, SO_RCVTIMEO, &milliseconds, sizeof(int));
}

void
socklib::inetsocket::setopt_sndtimeo(int milliseconds)
{
  setsockopt(SOL_SOCKET, SO_SNDTIMEO, &milliseconds, sizeof(int));
}

void
socklib::inetsocket::setsockopt(int level, int optname, const void* optval, int optlen)
{
  int err = ::setsockopt(sock, level, optname, (const char*)optval, optlen);
  if (err == SOCKET_ERROR)
    throw socklib::exception("sock:inetsocket:setsockopt: $s");
}

const char*
socklib::ipaddr2str(unsigned long ipaddr)
{
  struct in_addr sin;
  sin.s_addr = ipaddr;

  return inet_ntoa(sin);
}

std::string
socklib::ipaddr2string(unsigned long ipaddr)
{
  return ipaddr2str(ipaddr);
}

bool
socklib::is_valid_ipaddr(const char* ipaddrstr)
{
  // a.b.c.d
  const char* p = ipaddrstr;
  while(*p && !isdigit(*p)) p++;

  struct in_addr sin;
  sin.s_addr = inet_addr(p);

  const char* addr = inet_ntoa(sin);

  return strncmp(addr, p, strlen(addr)) == 0;
}

